<?php
namespace app\service;
use think\facade\Log;
use think\facade\Cache;

/**
 * 使用令牌桶算法进行限流
 * 令牌桶算法是一种常用的限流策略，它模拟了一个固定容量的桶，以恒定速率向其中添加令牌。每次收到请求时都会尝试从桶中取出一个令牌；如果桶为空，则拒绝该请求。
 * 小桶满时使用大桶
 */

class TokenBucketService{
	//小桶
	protected static $capacity_small = 100; // 桶的容量
	protected static $rate_small = 10; // 令牌生成速率，每秒生成的令牌数
	protected static $cachePrefix_small = 'token_bucket_small';

	//大桶
	protected static $capacity_large = 200; // 桶的容量
	protected static $rate_large = 20; // 令牌生成速率，每秒生成的令牌数
	protected static $cachePrefix_large = 'token_bucket_large';

	/**
	 * 令牌桶-小桶(混合动力限制器)
	 * 令牌桶算法是一种常用的限流策略，它模拟了一个固定容量的桶，以恒定速率向其中添加令牌。每次收到请求时都会尝试从桶中取出一个令牌；如果桶为空，则拒绝该请求。
	 * @param  [type] $key [ip地址]
	 * @return [type]      [description]
	 */
	public static function hybridlimiter($key){
		$bucketKey = self::$cachePrefix_small. $key;
		/**
		 * 从缓存中获取指定键的数据。如果键不存在，则可以提供一个默认值或回调函数来初始化该键的值。
		 * ['tokens' => $this->capacity, 'lastRefill' => time()]表示键不存在，则可以提供一个默认值
		 */
		$bucket = Cache::store('redis')->get($bucketKey , ['tokens' => self::$capacity_small, 'lastRefill' => time()]);
		$now = time();
		$tokensToAdd = floor(($now - $bucket['lastRefill']) * self::$rate_small);
		//定值中的最小值
		$bucket['tokens'] = min(self::$capacity_small, $bucket['tokens'] + $tokensToAdd);
		$bucket['lastRefill'] = $now;

		if ($bucket['tokens'] >= 1) {
			$bucket['tokens']--;
			Cache::store('redis')->set($bucketKey , $bucket);// 设置缓存

			//清空大桶
			//获取 Redis 实例
			$redis = Cache::store('redis')->handler();
			$redisResult=$redis->get(self::$cachePrefix_large. $key);
			if($redisResult){
				//删除单个键
				$redis->del(self::$cachePrefix_large. $key);
			}

			return true;
		}

		//令牌桶-大桶
		$result=$this->allowRequest($key);
		if($result){
			return true;
		}
		return false;
	}

	/**
	 * 令牌桶-大桶
	 * 令牌桶算法是一种常用的限流策略，它模拟了一个固定容量的桶，以恒定速率向其中添加令牌。每次收到请求时都会尝试从桶中取出一个令牌；如果桶为空，则拒绝该请求。
	 * @param  [type] $key [ip地址]
	 * @return [type]      [description]
	 */
	public static function allowRequest($key){
		$bucketKey = self::$cachePrefix_large. $key;
		/**
		 * 从缓存中获取指定键的数据。如果键不存在，则可以提供一个默认值或回调函数来初始化该键的值。
		 * ['tokens' => $this->capacity, 'lastRefill' => time()]表示键不存在，则可以提供一个默认值
		 */
		$bucket = Cache::store('redis')->get($bucketKey , ['tokens' => self::$capacity_large, 'lastRefill' => time()]);
		$now = time();
		$tokensToAdd = floor(($now - $bucket['lastRefill']) * self::$rate_large);
		//定值中的最小值
		$bucket['tokens'] = min(self::$capacity_large, $bucket['tokens'] + $tokensToAdd);
		$bucket['lastRefill'] = $now;

		if ($bucket['tokens'] >= 1) {
			$bucket['tokens']--;
			Cache::store('redis')->set($bucketKey , $bucket);// 设置缓存
			return true;
		}

		return false;
	}











}
